Skip to content

docs(protocol): publish the paged range-frame metadata split and -32009 - #48

Merged
MichaelTaylor3d merged 4 commits into
mainfrom
docs/1649-range-frame-coherence
Jul 26, 2026
Merged

docs(protocol): publish the paged range-frame metadata split and -32009#48
MichaelTaylor3d merged 4 commits into
mainfrom
docs/1649-range-frame-coherence

Conversation

@MichaelTaylor3d

@MichaelTaylor3d MichaelTaylor3d commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

What changed

Two user-facing surfaces published a range-frame contract and an error taxonomy that no longer match the protocol.

docs/protocol/peer-network.md §9 (#range) published range-frame metadata as first-frame-only. It now publishes the split:

  • The fixed-size identity setroot, total_length, chunk_count, plus chunk_index on a chunk-aligned window — rides every frame, with the reason a protocol implementer needs: root + total_length are what detect a wrong-generation holder as the first bytes arrive, and each is one scalar, so it costs the same on a 3-chunk resource as a million-chunk one.
  • The resource-scaling layout setchunk_lens, inclusion_proof — rides the first frame or a paged prologue, once per range stream, each page located by chunk_lens_offset, and is omitted entirely when the client sets skip_layout.
  • Why it is a split and not a slice, stated because an implementer who slices it ships a stream no client can decrypt: chunk_lens is a decrypt input, not a verify input — per-chunk AES-256-GCM-SIV needs the whole array, and a reader rejects any array whose entries do not sum to total_length. A per-chunk proof could not do the arrival-time check either, because merkle leaves commit per resource.
  • The five frame bounds as a table (MAX_FRAMED_BODY 65536, MAX_RANGE_FRAME_PAYLOAD 32768, MAX_INCLUSION_PROOF_B64 4096, MAX_CHUNK_LENS_PER_FRAME 2048, MAX_FIRST_FRAME_CHUNK_LENS 2486), and that a serve path splits on the payload cap, never the per-request 3 MiB window.
  • range_proof / first_chunk_index are now published as reserved (a server MUST NOT emit them). The page previously invited servers to emit them.
  • skip_layout added to the request params.

docs/support/error-codes.md adds -32009 RANGE_METADATA_UNREPRESENTABLE to the JSON-RPC prose list and table, with the client rule: holder-fatal — skip the holder for that range, do not re-request it, and do not count it as a transport failure (counted as transport, a client retries a peer that can never succeed). Also added to the §9 range-error table at protocol depth.

scripts/dig-spec.mjs is the generator behind static/openrpc*.json + static/error-codes.json, and the repo runs a build-time drift gate between that catalog and the prose table — so the machine artifacts had to move in the same commit. The RangeFrame schema, the dig.fetchRange method entry, and the error catalog all carry the new contract.

Why documenting -32009 now

dig-nat 0.13.0 is live on crates.io (verified against the crates.io API) and it is the crate every real producer and consumer of RangeFrame uses — its mux.rs already answers RANGE_METADATA_UNREPRESENTABLE, and its MAX_INCLUSION_PROOF_B64 is enforced at the encoder as of that release. So a client written against the shipped peer wire can receive this code today, and the holder-fatal-vs-transport distinction is exactly the thing a client gets wrong without documentation. dig-rpc-protocol 0.6.0 mirrors the constant, but it is a mirror of an already-published wire fact, not the thing that makes the code real. Documenting it is the coherent call; withholding it would leave a shipped, reachable error undocumented.

How verified

  • npm ci clean; npm run build green — onBrokenLinks / onBrokenAnchors are throw, so the new #range-errors anchor and every touched cross-reference are build-validated. Build ran through postbuild (14 sitemaps, 1,680 URLs).
  • npm run typecheck (tsc) exit 0; npm run lint (eslint .) exit 0; npm run test:unit 25 pass / 0 fail / 1 skipped.
  • npm run gen drift gate: 58 codes across 4 surfaces, gate passed.
  • Drift gate proven load-bearing, not assumed: with the change committed, deleting only the -32009 row from error-codes.md makes npm run gen throw; restoring it returns the tree clean and the gate green. The gate genuinely fails on the defect it claims to catch.

Blast radius checked

gitnexus is disabled in the loop (§2.0 temp override), so radius was established by ripgrep + direct read at origin/main, and it is a docs repo with no importable symbols:

  • Saturation search across docs/, scripts/, static/*.json, i18n/ for first frame, range_proof, first_chunk_index, chunk_lens. English hits: the #range section, the §9 range-error table, the multi-source pseudocode step 3, the range-integrity split step, the §10 summary row, and the generator schema — all updated. No other English page asserts the metadata rule.
  • Downstream generated artifacts (static/openrpc.json, static/openrpc-node.json, static/error-codes.json) are regenerated in-commit — static/knowledge-graph.json is regenerated too but is not in the diff, because regeneration produced byte-identical output, so the machine surface cannot lag the prose.
  • Risk: low. No code, no wire behaviour, no consumer API. The one non-trivial risk was a silent prose/JSON divergence, which the drift gate now forecloses.

Version

0.6.00.7.0minor. New published capability on a machine-consumable contract: error-codes.json gains a code and openrpc*.json gains chunk_count / chunk_lens_offset / skip_layout. An agent branching on the catalog sees an additive surface change, which is a minor, not a patch. Nothing was removed or renumbered, so it is not a major.

Deferred, deliberately

Per repo convention, English only. The 13 translated locales now describe the superseded first-frame-only rule (listed in the lane report); changing that policy is not this unit's call.

Follow-up commit on this PR

Three same-class fixes plus one artifact correction, after the gate PASS on ce764d7:

  • error-codes.md — the -32007 remediation said to read total_length "from the first range frame", the last first-frame-scoped framing of a field that now rides every frame. Now "any range frame".
  • peer-network.md #range-errors-32009 was seated above -32004, leaving that one table out of numeric order. Reseated after -32008. Out-of-order declaration is what hid the missing row this unit exists to close, so it is not worth reproducing on the page that documents it.
  • peer-network.md §7 "Peer RPC error codes" — this table enumerates the node-profile codes the peer methods add, and omitted -32009, which dig.fetchRange adds. Row added, ordered last. Found while reseating the other table: the same missing-row class as the defect this unit closes.
  • static/openrpc.json + static/openrpc-node.jsoninfo.version is stamped from package.json, so both still read 0.6.0 after the bump. Regenerated to 0.7.0; a machine artifact understating its own version is exactly the drift these generated specs exist to prevent.

Drift gate re-run: 58 codes across 4 surfaces, passed.

MichaelTaylor3d and others added 3 commits July 26, 2026 14:26
Co-Authored-By: Claude <noreply@anthropic.com>
Range-frame metadata divides by whether it scales with the resource. The
fixed-size identity set (root, total_length, chunk_count, plus chunk_index
on a chunk-aligned window) rides EVERY frame, because root + total_length
are what detect a wrong-generation holder as the first bytes arrive. The
resource-scaling layout set (chunk_lens, inclusion_proof) rides the first
frame or a paged prologue once per range stream, each page located by
chunk_lens_offset, and is omitted entirely when a client sets skip_layout.

chunk_lens is a decrypt input, not a verify input: per-chunk AES-GCM-SIV
needs the whole array and a reader rejects one whose entries do not sum to
total_length. That is why a large layout is paged rather than sliced, and
it is stated on the page because an implementer who slices it ships a
stream no client can decrypt.

Also publishes the five frame bounds, records that a serve path splits on
the payload cap rather than the per-request window, marks range_proof and
first_chunk_index reserved, and adds -32009 RANGE_METADATA_UNREPRESENTABLE
to the error taxonomy on both the prose page and the generated catalog
(with its holder-fatal, not-a-transport-failure client rule).

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
@MichaelTaylor3d MichaelTaylor3d changed the title docs: publish the paged range-frame metadata split and -32009 docs(protocol): publish the paged range-frame metadata split and -32009 Jul 26, 2026
@MichaelTaylor3d
MichaelTaylor3d marked this pull request as ready for review July 26, 2026 21:45

@MichaelTaylor3d MichaelTaylor3d left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: PASS (correctness gate, fresh context, read-only)

All seven checks green; zero prior review threads. Reviewed at ce764d7.

§4.3 — swept, not spot-checked

I ran a repo-wide sweep at the head SHA (not a spot-check of the touched hunks) for v0.N.N, pre-0., backward(s) compat, forward-compatib, no longer, previously, used to, deprecated, superseded, as of, and separately for first frame / first range frame / FIRST FRAME ONLY / range_proof / first_chunk_index across docs/, scripts/, and the four static/*.json. No version narration, drift commentary, or "this changed" phrasing survives on any range-frame surface, and the generator's schema descriptions were swept with the prose (OPTIONAL (v0.4.0+)RESERVED, FIRST FRAME ONLY → identity/layout-set wording). No internal-file references and no internal issue numbers were introduced. A reader arriving cold cannot tell the rule was ever different. The remaining version-string hits in the sweep are pre-existing and unrelated to this contract (digstore-version CI pins, the chia:// scheme note, a troubleshooting release floor) — out of scope.

Technical accuracy

Checked against the superproject SYSTEM.md RPC-contract row rather than reconstructed from the crates. Identity set (root, total_length, chunk_count, chunk_index when chunk-aligned) on every frame, layout set (chunk_lens, inclusion_proof) once per stream on a first frame or paged prologue located by chunk_lens_offset, suppressed by skip_layout — all stated correctly on the protocol page, in the §10 summary row, and in the generator. The why-a-split-not-a-slice reasoning is present and correct: chunk_lens as a decrypt input needing the whole array summing to total_length, and root + total_length as the fixed-size arrival-time wrong-generation check with the per-resource-leaf reason a per-chunk proof cannot substitute. All five bounds match the canonical values exactly (65536 / 32768 / 4096 / 2048 / 2486), and the split-on-payload-cap-never-the-3-MiB-window rule is stated. -32009 carries the do-not-count-as-transport clause in all three places it appears (protocol prose, #range-errors table, error catalog) — this is the clause whose absence makes a client retry a peer that can never succeed, so I checked each occurrence individually.

The four downstream first-frame claims are each correct now — range-integrity step 1 ("the stream's chunk_lens and the frame's chunk_index"), multi-source pseudocode step 3 ("from the range stream's layout"), the §10 Streaming + range summary row (rewritten to the split), and the RangeFrame schema description. I swept for a fifth and found one candidate, noted non-gating below.

Audience separation — held

error-codes.md received the code, its meaning and the client action only. The paging mechanics, the decrypt-input reasoning and the bounds table stayed on the protocol page. The one bound value that crosses (4096) is the cause of the error and an integrator cannot act on the code without it — that is meaning, not protocol internals.

The two judgement calls

reserved over optional for range_proof / first_chunk_index — right call, and the fail-closed one. dig-nat's RangeFrame has no such fields at all, and it is the type every real producer and consumer uses. Publishing them as "optional" would invite a server to emit and a client to depend on a field the shipped wire cannot carry; MUST NOT emit / MUST NOT require is the only statement that keeps both sides conforming, and it matches the SYSTEM.md row's RESERVED.

Documenting -32009 rather than deferring — the reasoning was sound at the time it was made, independent of dig-rpc-protocol 0.6.0 having since published. dig-nat 0.13.0 answers the code on the live peer wire and enforces MAX_INCLUSION_PROOF_B64 at the encoder, so the code is reachable by a client written today; the mirror reflects an already-live wire fact rather than creating it. The specific harm of withholding is concrete: without the holder-fatal-vs-transport distinction published, a client's default is to classify it as transport and retry a peer that can never succeed. Deferring would have left a reachable error undocumented with a known wrong default.

Generated artifacts

The static/*.json diffs are confined to the RangeFrame schema, the dig.fetchRange method entry, the two -32009 catalog rows, and the version field. I enumerated every changed JSON key — no unrelated content shifted, which is the place a regeneration hides an accident. The prose table and the generator catalog agree, and the drift gate was proven load-bearing by deletion-then-restore rather than assumed.

Version

0.6.00.7.0 minor is right. error-codes.json gains a code and openrpc*.json gain chunk_count / chunk_lens_offset / skip_layout — a machine-consumable contract gained surface additively (§6.2), which is more than a patch, and nothing was removed or renumbered, so not a major.

Two non-gating notes posted inline and resolved by me so they cannot block the merge. No gating findings; nothing bars the squash-merge.

Comment thread docs/support/error-codes.md Outdated
Comment thread docs/protocol/peer-network.md Outdated
…length to any frame

Three same-class fixes on the numbered surfaces this unit publishes:

- the -32007 remediation pointed at the FIRST range frame for total_length,
  the last first-frame-scoped framing of a field that now rides every frame;
- -32009 sat above -32004 in the range-error table, leaving that one table
  out of numeric order — the same out-of-order declaration that hid the
  missing row this unit exists to close;
- section 7's peer-RPC table enumerates the node-profile codes a peer method
  adds and omitted -32009, which dig.fetchRange adds.

Regenerating also picks up the OpenRPC info.version, which tracks
package.json and still read 0.6.0 after the bump — a machine artifact
understating its own version.

Co-Authored-By: Claude <noreply@anthropic.com>
@MichaelTaylor3d
MichaelTaylor3d merged commit 5f14ec8 into main Jul 26, 2026
8 checks passed
@MichaelTaylor3d
MichaelTaylor3d deleted the docs/1649-range-frame-coherence branch July 26, 2026 22:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant